Skip to main content


Trabalhando com datas, horários e temporizadores​


O package time faz parte da biblioteca padrão e oferece recursos para trabalhar com datas, horários, intervalos de tempo e execução de tarefas após determinados períodos. Além de consultar o horário atual, podemos pausar uma execução, calcular quanto tempo uma operação demorou e programar ações que devem acontecer uma única vez ou periodicamente.


Alguns desses recursos trabalham diretamente com channels, permitindo controlar a execução das tarefas junto com as goroutines.



Package time​


O package time possui tipos e funções para representar horários e controlar intervalos de tempo. Entre os principais recursos estão time.Now(), time.Duration, time.Sleep() (que já vimos), time.Timer e time.Ticker. Antes dos exemplos, precisamos lembrar de um detalhe sobre o funcionamento do Go.


Todo programa Go já começa com uma goroutine principal, criada automaticamente pelo runtime. É nessa goroutine que a função main() é executada. O runtime é a parte do Go responsável por gerenciar recursos durante a execução do programa, incluindo goroutines, memória e temporizadores. Por isso, não precisamos escrever go main() ou criar manualmente uma goroutine para executar o programa.


Quando usamos time.Sleep() dentro de main(), estamos pausando a goroutine principal, já outras goroutines, que já estiverem em execução podem continuar trabalhando normalmente.



Trabalhando com datas e horários usando time.Now()​


A função time.Now() retorna a data e o horário atuais, representados por um valor do tipo time.Time.

package main

import (
"fmt"
"time"
)

func main() {
// Obtém a data e o horário atuais.
agora := time.Now()

fmt.Println("Agora:", agora)

// Formata a data e o horário.
fmt.Println(agora.Format("02/01/2006 15:04:05"))
}

O método Format() permite definir como a data será apresentada. Em Go, a formatação usa uma data de referência específica, 02/01/2006 15:04:05, em vez de símbolos como DD/MM/YYYY. Assim, o exemplo apresenta a data no formato dia, mês e ano, seguida de horas, minutos e segundos.



time.Duration​


O tipo time.Duration representa uma duração, ou seja, um intervalo de tempo. Ele é usado quando precisamos informar quanto tempo uma operação deve esperar ou calcular quanto tempo ela demorou. O Go oferece constantes que facilitam a definição desses intervalos.

time.Nanosecond
time.Microsecond
time.Millisecond
time.Second
time.Minute
time.Hour

Podemos multiplicar essas constantes para representar diferentes durações.

// Representa dois segundos.
duracao := 2 * time.Second

// Representa 500 milissegundos.
duracaoCurta := 500 * time.Millisecond

// Representa um minuto e 30 segundos.
duracaoLonga := time.Minute + 30*time.Second

Internamente, time.Duration representa a duração como uma quantidade de nanossegundos. Isso significa que time.Duration(5) representa apenas cinco nanossegundos, e não cinco segundos. Para representar cinco segundos, usamos 5 * time.Second.



Pausando a execução com time.Sleep()​


A função time.Sleep() pausa a goroutine atual durante o intervalo informado.

package main

import (
"fmt"
"time"
)

func main() {
fmt.Println("Iniciando...")

// Pausa a goroutine principal por dois segundos.
time.Sleep(2 * time.Second)

fmt.Println("Continuando...")
}

O programa mostra a primeira mensagem, espera aproximadamente dois segundos e depois mostra a segunda. O time.Sleep() não pausa todas as goroutines do programa, apenas aquela que executou a função. Também não precisamos criar um channel para usar Sleep(), porque o próprio runtime controla essa espera.



Calculando intervalos com time.Since() e time.Sub()​


Podemos calcular quanto tempo uma operação demorou usando time.Since() ou o método Sub(). A função time.Since() calcula o intervalo entre um instante informado e o momento atual.

package main

import (
"fmt"
"time"
)

func main() {
// Registra o início da operação.
inicio := time.Now()

time.Sleep(2 * time.Second)

// Calcula o tempo decorrido desde o início.
duracao := time.Since(inicio)

fmt.Println("Tempo decorrido:", duracao)
}

Resultado:

Tempo decorrido: 2.001035298s

Já o método Sub() calcula a diferença entre dois valores do tipo time.Time.

inicio := time.Now()

time.Sleep(2 * time.Second)

fim := time.Now()

// Calcula a diferença entre os dois horários.
duracao := fim.Sub(inicio)

fmt.Println(duracao)

Tempo decorrido: 2.001026001s

As duas operações retornam um time.Duration. A diferença é que time.Since(inicio) usa o momento atual como referência, enquanto fim.Sub(inicio) permite comparar dois horários específicos.



Executando tarefas após um intervalo com time.Timer​


O time.Timer representa um temporizador que dispara uma única vez, depois de um intervalo definido. Criamos um temporizador usando time.NewTimer().

package main

import (
"fmt"
"time"
)

func main() {
// Cria um temporizador de dois segundos.
timer := time.NewTimer(2 * time.Second)
defer timer.Stop()

fmt.Println("Aguardando...")

// Aguarda o temporizador disparar.
<-timer.C

fmt.Println("Tempo encerrado")
}

Nesse exemplo, time.NewTimer() cria um temporizador e disponibiliza um channel chamado C. Esse channel recebe um valor do tipo time.Time quando o intervalo termina.

<-timer.C

A expressão acima aguarda o recebimento de um valor do channel. Enquanto isso, a goroutine atual fica bloqueada, sem impedir que outras goroutines sejam executadas. É aqui que o package time cria um channel internamente. Não precisamos escrever make(chan time.Time), porque time.NewTimer() já fornece esse channel.


O temporizador dispara uma única vez, para esperar outro intervalo usando o mesmo Timer, precisamos reiniciá-lo. Para entender melhor como funciona:

  1. O time.NewTimer(2 * time.Second) vai iniciar a contagem de dois segundos.
  2. O fmt.Println("Aguardando...") mostra a mensagem imediatamente.
  3. Ai a goroutine fica esperando um retorno do channel: <-timer.C.
  4. Quando os dois segundos terminam, o temporizador envia um valor para o channel C.
  5. A goroutine é desbloqueada e executa fmt.Println("Tempo encerrado").


Executando tarefas periodicamente com time.Ticker​


O time.Ticker funciona de maneira parecida com time.Timer, mas possui uma diferença importante. Enquanto o Timer dispara uma única vez, o Ticker dispara repetidamente, em intervalos definidos. Podemos criar um Ticker usando time.NewTicker().

package main

import (
"fmt"
"time"
)

func main() {
// Cria um Ticker que dispara a cada segundo.
ticker := time.NewTicker(time.Second)
defer ticker.Stop()

// Executa a tarefa três vezes.
for i := 1; i <= 3; i++ {
<-ticker.C

fmt.Println("Executando tarefa:", i)
}
}

Resultado:

Executando tarefa: 1
Executando tarefa: 2
Executando tarefa: 3

O Ticker possui um channel chamado C, que recebe um valor a cada intervalo. Assim como acontece com o Timer, o package time cria e fornece esse channel automaticamente. Quando usamos:

<-ticker.C

A goroutine espera até que o próximo intervalo termine. Depois disso, continua a execução. Se a tarefa demorar mais que o intervalo configurado, o Ticker poderá descartar alguns disparos para acompanhar o tempo. Por isso, ele não garante que todos os intervalos resultem em uma execução.



Usando time.After()​


A função time.After() oferece uma maneira mais simples de esperar um intervalo usando um channel. Internamente, ela cria um temporizador e retorna seu channel, funcionando de maneira equivalente a time.NewTimer(d).C.

package main

import (
"fmt"
"time"
)

func main() {
fmt.Println("Aguardando...")

// Aguarda dois segundos pelo channel retornado.
<-time.After(2 * time.Second)

fmt.Println("Tempo encerrado")
}

Aqui, não precisamos criar uma variável para armazenar o temporizador. O time.After() é bastante usado com select, principalmente quando queremos estabelecer um limite de espera para alguma operação.

package main

import (
"fmt"
"time"
)

func main() {
// Channel que receberá o resultado de uma operação.
resultado := make(chan string, 1)

go func() {
// Simula uma operação demorada.
time.Sleep(2 * time.Second)

resultado <- "Operação concluída"
}()

select {
case mensagem := <-resultado:
fmt.Println(mensagem)

case <-time.After(time.Second):
fmt.Println("Tempo de espera esgotado")
}
}

Resultado:

Tempo de espera esgotado

Nesse exemplo, a operação demora dois segundos, mas o select espera apenas um segundo. Como o tempo termina antes de o resultado chegar, o programa mostra a mensagem de tempo esgotado. Observe que time.After() não cancela a goroutine responsável pela operação. Ele apenas permite interromper a espera pelo resultado.


Usamos um channel com buffer para que a goroutine consiga enviar o resultado mesmo depois que a função main() deixar de recebê-lo. Nesse exemplo, o encerramento de main() também encerra o programa.



Interrompendo e reiniciando temporizadores com Stop() e Reset()​


Tanto o Timer quanto o Ticker possuem métodos para interromper e reiniciar seus intervalos. O método Stop() interrompe o temporizador, impedindo novos disparos. Já o método Reset() permite configurar novamente o intervalo. Por exemplo, podemos criar um Timer, interrompê-lo e depois reiniciá-lo com outro intervalo.

// A partir da linha abaixo, o timer já esta contando:
timer := time.NewTimer(3 * time.Second)

// Interrompe o temporizador antes de disparar.
timer.Stop()

// Reinicia o temporizador para um segundo.
timer.Reset(time.Second)

// Aguarda o novo disparo.
<-timer.C

fmt.Println("Temporizador encerrado")

Aqui, o temporizador foi criado inicialmente com três segundos, mas interrompemos sua execução e configuramos um novo intervalo de um segundo. O Timer.Stop() retorna um bool, indicando se conseguiu interromper um temporizador que ainda estava ativo. Já Timer.Reset() também retorna um bool, indicando se ele estava ativo antes da reinicialização.


No caso do Ticker, podemos alterar o intervalo de execução usando Reset().

ticker := time.NewTicker(time.Second)

// Altera o intervalo para dois segundos.
ticker.Reset(2 * time.Second)

// Interrompe os disparos.
ticker.Stop()

O Ticker.Reset() também pode reiniciar um ticker que foi interrompido, enquanto Ticker.Stop() não retorna nenhum valor. Um cuidado é que Stop() não fecha o channel do temporizador. Portanto, tentar receber de timer.C ou ticker.C depois de interrompê-los pode deixar a goroutine bloqueada.


Nas versões modernas do Go, a partir do comportamento introduzido no Go 1.23, Timer.Stop() e Timer.Reset() também oferecem garantias melhores para evitar o recebimento de valores antigos do temporizador. Em projetos com a semântica anterior, esse controle exige cuidados adicionais.



Trabalhando com Timers, Tickers e Channels​


Como vimos, Timer e Ticker possuem channels internos. Isso permite combiná-los com o select, controlando diferentes eventos dentro da mesma goroutine. Por exemplo, podemos executar uma tarefa periodicamente, mas interromper sua execução depois de um tempo determinado.

package main

import (
"fmt"
"time"
)

func main() {
// Executa uma tarefa a cada 500 milissegundos.
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()

// Define um limite de dois segundos.
timer := time.NewTimer(2 * time.Second)
defer timer.Stop()

for {
select {
case <-ticker.C:
// Executa quando o Ticker dispara.
fmt.Println("Executando tarefa...")

case <-timer.C:
// Executa quando o Timer dispara.
fmt.Println("Tempo limite atingido")
return
}
}
}

Resultado:

Executando tarefa...
Executando tarefa...
Executando tarefa...
Executando tarefa...
Tempo limite atingido

Aqui temos dois channels sendo observados pelo mesmo select. O ticker.C recebe valores periodicamente, permitindo executar a tarefa a cada 500 milissegundos. Já o timer.C recebe um valor após aproximadamente dois segundos. Quando isso acontece, o programa mostra a mensagem de encerramento e sai da função main().


Observe que não criamos nenhuma goroutine manualmente nesse exemplo, porque o select consegue esperar pelos dois channels dentro da goroutine principal. O runtime do Go é responsável por gerenciar os temporizadores e disponibilizar seus disparos nos respectivos channels. Isso não significa que uma nova goroutine seja criada para cada Timer ou Ticker.


Também precisamos distinguir o comportamento das funções. time.Now() apenas consulta o horário atual, enquanto time.Sleep() pausa a goroutine atual. Nenhuma delas disponibiliza um channel. Já time.NewTimer(), time.NewTicker() e time.After() trabalham com channels criados internamente, que permitem receber notificações quando os intervalos terminam.


Essa diferença explica por que Sleep() é usado quando queremos apenas esperar, enquanto Timer, Ticker e After permitem combinar a passagem do tempo com outros eventos usando channels e select.